/*********************************************************************************
 *      Copyright:  (C) 2022 dairongan<2974932606@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  i2c_sht20.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(07/21/2022)
 *         Author:  dairongan <2974932606@qq.com>
 *      ChangeLog:  1, Release initial version on "07/21/2022 11:35:53 AM"
 *                 
 ********************************************************************************/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/device.h> 
#include <linux/of_gpio.h> 
#include <linux/semaphore.h> 
#include <linux/timer.h> 
#include <linux/i2c.h> 
#include <asm/mach/map.h> 
#include <asm/uaccess.h> 
#include <asm/io.h>

#define SHT20_CNT	1
#define SHT20_NAME	"sht20"
#define SHT20_TEMP	0xf3	//无主机模式触发温度
#define SHT20_HUM	0xf5	//无主机模式触发湿度
#define CRC_MODEL	0x131
#define CRC_SUCCESS 0
#define CRC_FAIL	1

struct sht20_dev{
	dev_t	devid;
	struct	cdev cdev;
	struct	class *class;
	struct	device *device;
	struct	device_node *nd;
	int 	major;
	void	*private_data;
	unsigned short temp,hum;	//温湿度数据
};

static struct sht20_dev sht20dev;

static int crc_check(unsigned char *data, int len, unsigned char checksum)
{
    unsigned char   crc = 0x00; 
    int             i, j;  
 
    for(i=0; i<len; i++)
    {
        crc ^= *data++;  		   
		
        for (j=0; j<8; j++)     
        { 
            if (crc & 0x80)
			{
				crc = (crc << 1) ^ CRC_MODEL;
			}    
            else
            {
                crc = (crc << 1);
            }
        }
    }
    // printk("crc clu data : [%x]\n", crc);
 
    if(checksum == crc)
	{
        return CRC_SUCCESS;
	}
	else 
    {
        return CRC_FAIL;
    }
}

static unsigned short sht20_read_temp(struct i2c_client *client)
{
	int rv=0;
	unsigned short temp;
	u8 data[3]={0};
	char cmd=SHT20_TEMP;
	
	if(!client)
	{
		printk("%s line [%d] %s() get invalid input arguments\r\n", __FILE__, __LINE__, __func__);
		return -1;
	}

	rv = i2c_master_send(client,&cmd,1);
	if(rv<0)
	{
		dev_err(&client->dev,"i2c send temperature cmd failure\r\n");
		return -1;
	}

	msleep(85);

	if(CRC_SUCCESS != crc_check(data,2,data[2]))
	{
		dev_err(&client->dev,"temperature data failure to pass cyclic redundancy check\r\n");
		return -1;
	}

	rv = i2c_master_recv(client,data,sizeof(data));
	if(rv<0)
	{
		dev_err(&client->dev,"i2c recv temperature data failure\r\n");
		return -1;
	}

	temp = (data[0]<<8)|(data[1]&0xFC);	
	printk("temp=%d\r\n",temp);


	return temp;
}

static unsigned short sht20_read_hum(struct i2c_client *client)
{
	int rv=0;
    unsigned short hum;
	u8 data[3]={0};
	char cmd=SHT20_HUM;
	
	if(!client)
	{
		printk("%s line [%d] %s() get invalid input arguments\r\n", __FILE__, __LINE__, __func__);
		return -1;
	}

	rv = i2c_master_send(client,&cmd,1);
	if(rv<0)
	{
		dev_err(&client->dev,"i2c send humidity cmd failure\r\n");
		return -1;
	}

	msleep(29);

	if(CRC_SUCCESS != crc_check(data,2,data[2]))
	{
		dev_err(&client->dev,"humidity data failure to pass cyclic redundancy check\r\n");
		return -1;
	}

	rv = i2c_master_recv(client,data,sizeof(data));
	if(rv<0)
	{
		dev_err(&client->dev,"i2c recv humidity data failure\r\n");
		return -1;
	}

	hum = (data[0]<<8)|(data[1]&0xFC);	
	printk("hum=%d\r\n",hum);
	return hum;
}


static int sht20_open(struct inode *inode,struct file *filp)
{
	filp->private_data = &sht20dev;
	return 0;
}

static ssize_t sht20_read(struct file *filp,char __user *buf,size_t cnt,loff_t *off)
{
	unsigned short data[2];
	long err=0;

	struct sht20_dev *dev = (struct sht20_dev *)filp->private_data;

	data[0]=sht20_read_temp(dev->private_data);
	data[1]=sht20_read_hum(dev->private_data);

	dev->temp=data[0];
	dev->hum=data[1];

	err = copy_to_user(buf,data,sizeof(data));

	return 0;
}

static int sht20_release(struct inode *inode,struct file *filp)
{
	return 0;
}

static const struct file_operations sht20_ops = {
	.owner = THIS_MODULE,
	.open = sht20_open,
	.read = sht20_read,
	.release = sht20_release,
};

static ssize_t sht20_show(struct device *dev,struct device_attribute *attr,char *buf)
{
	unsigned short temp,hum;
	struct sht20_dev *ddev = dev_get_drvdata(dev);

	if(!ddev->private_data)
	{
		printk("failure to get i2c_client \r\n");
		return -EFAULT;
	}

	temp = sht20_read_temp(ddev->private_data);
	if(temp)
	{
		dev_err(ddev->device,"read temperature failure\r\n");
	}

	printk("zhe li ok\r\n");

	hum = sht20_read_hum(ddev->private_data);
	if(hum)
	{
		dev_err(ddev->device,"read humidity failure\r\n");
	}
	
	printk("zhe li ok too\r\n");

	return snprintf(buf,PAGE_SIZE,"temperature=%d humidity=%d\r\n",temp,hum);
}

static ssize_t sht20_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    char k_buf[10] = {0,};
    snprintf(k_buf, sizeof(k_buf), "%s", buf);

    dev_info(dev, "Don't echo to me  -> [%s] size [%d]\n", k_buf, count);

    return count;
}

static DEVICE_ATTR(sht20_temp_hum,0644,sht20_show,sht20_store);


static int sht20_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
	if(sht20dev.major)
	{
		sht20dev.devid = MKDEV(sht20dev.major,0);
		register_chrdev_region(sht20dev.devid,SHT20_CNT,SHT20_NAME);
	}
	else
	{
		alloc_chrdev_region(&sht20dev.devid,0,SHT20_CNT,SHT20_NAME);
		sht20dev.major = MAJOR(sht20dev.devid);
	}

	cdev_init(&sht20dev.cdev,&sht20_ops);
	cdev_add(&sht20dev.cdev,sht20dev.devid,SHT20_CNT);

	sht20dev.class = class_create(THIS_MODULE,SHT20_NAME);
	if(IS_ERR(sht20dev.class))
	{
		return PTR_ERR(sht20dev.class);
	}

	sht20dev.device = device_create(sht20dev.class,NULL,sht20dev.devid,NULL,SHT20_NAME);
	if(IS_ERR(sht20dev.device))
	{
		return PTR_ERR(sht20dev.device);
	}

	//创建sys属性 在platform下
	if(device_create_file(sht20dev.device,&dev_attr_sht20_temp_hum))
	{
		return -ENOMEM;
	}

	sht20dev.private_data=client;

	printk("insmod successfully!\r\n");
	return 0;
}

static int sht20_remove(struct i2c_client *client)
{
	device_remove_file(sht20dev.device,&dev_attr_sht20_temp_hum);
	cdev_del(&sht20dev.cdev);
	unregister_chrdev_region(sht20dev.devid,SHT20_CNT);

	device_destroy(sht20dev.class,sht20dev.devid);
	class_destroy(sht20dev.class);

	return 0;
}


static const struct i2c_device_id sht20_id[]={
	{"ssr,sht20",0},
	{}
};

static const struct of_device_id sht20_of_match[]={
	{.compatible = "ssr,sht20"},
	{}
};

static struct i2c_driver sht20_driver = {
	.probe = sht20_probe,
	.remove = sht20_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "sht20",
		.of_match_table = sht20_of_match,
	},
	.id_table = sht20_id,
};

static int __init sht20_init(void)
{
	int ret = 0;
	ret = i2c_add_driver(&sht20_driver);
	return ret;
}

static void __exit sht20_exit(void)
{
	i2c_del_driver(&sht20_driver);
}

module_init(sht20_init);
module_exit(sht20_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dairongan");
